﻿namespace SpikingAnalyticsLib

open System.IO
open System.Collections.Generic
open NetworkUtilities
open SpikingNeuronLib

//===========================================================================================================

/// The version of the FindPolychronousGroups algorithm
type FindGroupsVersion = 
    | Original = 0
    | Current = 1

//===========================================================================================================

/// A type for maturing networks and scanning folders of network state files for Polychronous Neural Groups
[<AbstractClass; Sealed>]
type PNGScanner () =

    // create a map of file paths in the specified folder
    // file paths are keyed by an integer extracted from the filename
    // assumes filenames are of the form: xxxxxxxNNN.txt where NNN is an integer
    static let GetStateFilesMap folderPath basename =

        let GetIndex (filename:string) (basename:string) =
            let startIndex = basename.Length
            let length = filename.IndexOf('.') - startIndex
            let temp =
                let temp = filename.Substring(startIndex, length)
                let chars =
                    temp.ToCharArray()
                    |> Seq.filter (fun ch -> System.Char.IsDigit(ch))
                    |> Seq.toArray
                new System.String(chars)
            let result =
                try
                    Some(int(temp))
                with
                    | ex ->
                        printfn "Warning: No number to extract in %s\nOriginal exception: %s" filename ex.Message
                        None

            result

        let fileNames = Directory.GetFiles(folderPath, "*.txt")
        fileNames
        |> Seq.map (fun filePath -> Path.GetFileName(filePath), filePath)
        |> Seq.filter (fun (filename, filePath) -> filename.StartsWith(basename))
        |> Seq.map (fun (filename, filePath) ->
                let fileIndex = GetIndex filename basename
                if fileIndex.IsSome then
                    Some(fileIndex.Value, filePath)
                else
                    None
            )
        |> Seq.choose id
        |> Map.ofSeq

    // Create a new network with the event bindings needed for saving maturation files
    static let CreateEventBoundNetworkImpl (networkSpecifier:IzhikevichNetworkSpecifier) outputFolder baseName saveState saveInterval =

        let _spn = new IzhikevichNetwork(networkSpecifier.NumExcitatoryNeurons, networkSpecifier.NumInhibitoryNeurons,
                    networkSpecifier.NumSynapsesPerNeuron, networkSpecifier.MaxDelay)

        let oneSecTickBeforeCleanupHandler =
            fun sec frate maximal ifrate ->
                if sec % 60 = 0 then        // report every one minute
                    printfn "%d %f %f %f" sec frate maximal ifrate

        let oneSecTickAfterCleanupHandler =
            fun sec ->
                if saveState then
                    if sec % saveInterval = 0 then
                        if (sec > 0) then       // save every 'saveInterval' seconds
                            let fname =
                                let mins = sec / 60
                                sprintf "%s%d.txt" baseName mins
                            // Note that network state must be saved AFTER cleanup
                            _spn.SaveNetworkState(Path.Combine(outputFolder, fname))

        _spn.add_OneSecondTickBeforeCleanup(new OneSecondTickBeforeCleanupEventHandler(oneSecTickBeforeCleanupHandler))
        _spn.add_OneSecondTickAfterCleanup(new OneSecondTickAfterCleanupEventHandler(oneSecTickAfterCleanupHandler))
        _spn.add_RunWarning(new RunWarningEventHandler(fun msg -> printfn "%s" msg))

        _spn

    // Create a new network with the event bindings needed for saving maturation files and collecting PNGs
    // Note that the saveState is captured for use in the oneSecTickAfterCleanupHandler
    static let CreatePNGCollectorNetworkImpl (networkSpecifier:IzhikevichNetworkSpecifier) outputFolder baseName tripletCombinations (fBase:StreamWriter) (fGroups:StreamWriter) saveState =

        let _spn = new IzhikevichNetwork(networkSpecifier.NumExcitatoryNeurons, networkSpecifier.NumInhibitoryNeurons,
                    networkSpecifier.NumSynapsesPerNeuron, networkSpecifier.MaxDelay)

        let oneSecTickBeforeCleanupHandler =
            fun sec frate maximal ifrate ->
                if sec % 60 = 0 then        // report every one minute
                    let mins = sec / 60
                    printfn "%d %f %f %f" mins frate maximal ifrate
                    printfn "Searching for groups..."
                    // Note: uses current algorithm
                    _spn.FindPolychronousGroupsFromTripletList(string mins, tripletCombinations)
                    |> ignore

        let oneSecTickAfterCleanupHandler =
            fun sec ->
                if saveState && (sec % 60 = 0) then
                    if (sec > 0) then       // save every one minute
                        let fname =
                            let mins = sec / 60
                            printfn "Completed %d mins" mins
                            printfn "Saving state..."
                            sprintf "%s%d.txt" baseName mins
                        // Note that network state must be saved AFTER cleanup
                        _spn.SaveNetworkState(Path.Combine(outputFolder, fname))

        let groupFoundHandler =
            fun tag totalNumberOfGroups anchorNeuron firedNeurons maxLayer lastSpike (pngData:PNGData) ->
                printfn "mins=%s: anchor=%d ngroups=%d totalFired=%d maxLayers=%d lastSpike=%d\n" tag anchorNeuron totalNumberOfGroups firedNeurons maxLayer lastSpike
                fBase.WriteLine("{0} {1} {2} {3} {4}\n", tag, totalNumberOfGroups, firedNeurons, maxLayer, lastSpike)
                fGroups.WriteLine("{0} {1}", tag, totalNumberOfGroups)
                let events = pngData.GetPNGEvents(firedNeurons)
                for event in events do
                    fGroups.Write("{0}, ", event)
                fGroups.WriteLine()
                fGroups.WriteLine("{0}", String.replicate 70 "-")
                let links = pngData.GetPNGLinks()
                for link in links do
                    fGroups.Write("{0}, ", link)
                fGroups.WriteLine()
                fGroups.WriteLine("{0}", String.replicate 70 "=")
                printfn "%s" (String.replicate 70 "=")

        _spn.add_FoundPNG(new FoundPNGEventHandler(groupFoundHandler))
        _spn.add_OneSecondTickBeforeCleanup(new OneSecondTickBeforeCleanupEventHandler(oneSecTickBeforeCleanupHandler))
        _spn.add_OneSecondTickAfterCleanup(new OneSecondTickAfterCleanupEventHandler(oneSecTickAfterCleanupHandler))
        _spn.add_RunWarning(new RunWarningEventHandler(fun msg -> printfn "%s" msg))

        _spn

    // Create a new network with the event bindings needed for collecting the initial triplets of all PNGs in the network
    static let CreatePNGTripletsCollectorNetworkImpl (networkSpecifier:IzhikevichNetworkSpecifier) (fTriplets:StreamWriter) =

        let _spn = new IzhikevichNetwork(networkSpecifier.NumExcitatoryNeurons, networkSpecifier.NumInhibitoryNeurons,
                    networkSpecifier.NumSynapsesPerNeuron, networkSpecifier.MaxDelay)

        let groupFoundHandler =
            fun tag totalNumberOfGroups anchorNeuron firedNeurons maxLayer lastSpike (pngData:PNGData) ->
                printfn "mins=%s: anchor=%d ngroups=%d totalFired=%d maxLayers=%d lastSpike=%d\n" tag anchorNeuron totalNumberOfGroups firedNeurons maxLayer lastSpike
                let events = pngData.GetPNGEvents(firedNeurons)
                // take first three events
                events
                |> Seq.take 3
                |> Seq.iter (fun event ->
                        fTriplets.Write("{0} {1} ", event.Time, event.NeuronIndex)
                    )
                fTriplets.WriteLine()
                fTriplets.Flush()

        _spn.add_FoundPNG(new FoundPNGEventHandler(groupFoundHandler))
        _spn.add_RunWarning(new RunWarningEventHandler(fun msg -> printfn "%s" msg))

        _spn

    static let MatchingNetworkFactory (networkSpecifier:IzhikevichNetworkSpecifier) (scanner:MatchScanner) (matchesWriter:StreamWriter) stateFilePath =
        let network = new IzhikevichNetwork(networkSpecifier.NumExcitatoryNeurons, networkSpecifier.NumInhibitoryNeurons, networkSpecifier.NumSynapsesPerNeuron, networkSpecifier.MaxDelay)

        let oneSecTickBeforeCleanupHandler =
            fun sec frate maximal ifrate ->
                let firings = network.GetFiringsData(false)
                let firingEventList = new List<FiringEvent>(firings)
                scanner.AddData(sec, firingEventList)

        let matchedHandler =
            fun (matchResult:MatchResult) ->
                printfn ""
                printfn "%s %d %d %d %d %d %s" matchResult.Tag matchResult.TemplateID matchResult.UniqueID matchResult.Index matchResult.Offset matchResult.MatchCount matchResult.MatchPattern
                printfn ""
                matchesWriter.WriteLine("{0} {1} {2} {3} {4} {5} {6}", matchResult.Tag, matchResult.TemplateID, matchResult.UniqueID, matchResult.Index, matchResult.Offset, matchResult.MatchCount, matchResult.MatchPattern)
                matchesWriter.WriteLine("{0}", scanner.Templates.[matchResult.TemplateID])
                //scanner.SaveData("%s\%s.txt" % (destinationPath, matchedFileKey), matchResult.FileIndex)

        network.add_OneSecondTickBeforeCleanup(new OneSecondTickBeforeCleanupEventHandler(oneSecTickBeforeCleanupHandler))
        network.add_RunWarning(new RunWarningEventHandler(fun msg -> printfn "%s" msg))
        scanner.add_Matched(new MatchedEventHandler(matchedHandler))
        scanner.add_ScanUpdate(new ScanUpdateEventHandler(fun sec nMatches -> printf "."))   // a dot for each completed second of data

        network.LoadNetworkState(stateFilePath)
        network

    static let MatchingNetworkExecutorSinglePattern inputPattern runSeconds patternGenerator (network:IzhikevichNetwork) =
        network.Run(runSeconds, patternGenerator, inputPattern);

    static let MatchingNetworkExecutorMultiPattern (patterns:Stimulus list) runSeconds secsPerPattern patternGenerator (network:IzhikevichNetwork) =

        let RunAllPatterns secsOfExecution =
            let executionTime =
                patterns
                |> Seq.mapi (fun index pattern ->
                        printf "%d " index
                        let startSeconds = (index * secsPerPattern) + secsOfExecution
                        let endSeconds = startSeconds + secsPerPattern
                        network.Run(startSeconds, endSeconds, patternGenerator, pattern)
                        secsPerPattern
                    )
                |> Seq.sum
            secsOfExecution + executionTime

        let rec loop secsOfExecution =
            let secsOfExecution = RunAllPatterns secsOfExecution (*Repeat*)
            if secsOfExecution >= runSeconds then ()   (*Until*)
            else loop (secsOfExecution)
        loop 0

    static let NetworkActivationScanner (networkFactory:string -> IzhikevichNetwork) (networkExecutor:IzhikevichNetwork -> unit) (scanner:MatchScanner) (templateList:TemplateList) thresholdPercent minMatchCount sampleTime stateFilePath =

        scanner.Reset()                                 // clear everything
        for template in templateList.Patterns do        // add back ALL templates
            let matchCount =
                // match ALL events for small templates
                if template.Count < minMatchCount then
                    template.Count
                else
                    // for larger templates match at least the minimum, or thresholdPercent, whichever is larger
                    let thresholdCount = (thresholdPercent * template.Count) / 100
                    if thresholdCount > minMatchCount then
                        thresholdCount
                    else
                        minMatchCount
            scanner.AddTemplate(template, matchCount)

        printfn "Mins=%d (%d templates): %s" sampleTime scanner.Templates.Count (Path.GetFileName(stateFilePath))

        printf "Initializing..."
        let network = networkFactory stateFilePath
        printfn "done"

        printf "Running..."
        networkExecutor network
        printfn "done"

        printf "Scanning"
        scanner.Match(string sampleTime)
        printfn "done"

    // Scan the input folder for PNG templates using the specified network scanner
    static let ScanForActivatedGroupsImpl networkScanner inputFolder baseName =

        let filesMap = GetStateFilesMap inputFolder baseName

        for kvp in filesMap do              // normally ALL one minute samples over an hour
            let sampleTime, stateFilePath = kvp.Key, kvp.Value
            networkScanner sampleTime stateFilePath

    static let CreateMatureIzhikevichNetwork (spn:NetworkBase) inputPattern runSeconds backgroundFiringRate outputFolder baseName =

        let patternGenerator =
            if backgroundFiringRate > 0 then
                Some(new PoissonProcessPatternGenerator(spn.TotalNeurons, backgroundFiringRate))
            else
                None

        printfn "Starting..."
        let success =
            try
                RunNetwork spn runSeconds patternGenerator inputPattern     // saves state every minute
                true
            with
                | ex ->
                    printfn "Caught an exception: %s" (ex.Message)
                    spn.SaveNetworkState(Path.Combine(outputFolder, "incomplete.txt"))
                    printfn "Network state saved"
                    false

        if success then
            let mins = runSeconds / 60
            spn.SaveNetworkState(Path.Combine(outputFolder, sprintf "%s_%d.txt" baseName mins))

    static let CreatePNGAnchorMap (spn:IzhikevichNetwork) maxAnchor algorithmVersion collectDetails =

        // select the algorithm version
        let algorithm:(string * int -> int) =
            match algorithmVersion with
            | FindGroupsVersion.Original -> spn.FindPolychronousGroupsOriginalVersion
            | FindGroupsVersion.Current -> spn.FindPolychronousGroups
            | _ -> failwith "Unspecified algorithm"

        let detailMap =
            if collectDetails then
                let dict = new Dictionary<int, (int * int * int) list>()

                let onFoundPNGHandler =
                    fun tag numPNGs anchorNeuron numFired maxLinks maxTime pngData ->
                        printfn "%d %d %d %d %d" numPNGs anchorNeuron numFired maxLinks maxTime
                        let anchorList =
                            if dict.ContainsKey(anchorNeuron) then
                                dict.[anchorNeuron]
                            else
                                []
                        dict.[anchorNeuron] <- (numFired, maxLinks, maxTime) :: anchorList

                spn.add_FoundPNG(new FoundPNGEventHandler(onFoundPNGHandler))
                Some(dict)
            else
                None

        spn.add_RunWarning(new RunWarningEventHandler(fun message -> printfn "Warning: %s" message))

        let pngCountMap =
            seq {
                for i in 0..maxAnchor-1 do
                    printfn "\tAnalysing anchor %d..." i
                    let numPNGs = algorithm(null, i)
                    yield i, numPNGs
            }
            |> Map.ofSeq

        let total =
            pngCountMap
            |> Map.toSeq
            |> Seq.sumBy (fun (neuron, count) -> count)

        total, pngCountMap, detailMap

    static let WriteLinesToFile outputFolder fileName (lines:seq<string>) =
        let outputPath = Path.Combine(outputFolder, fileName)
        File.WriteAllLines(outputPath, lines)

    static let FindAllPNGs (spn:IzhikevichNetwork) outputFolder outputFile algorithmVersion =

        let numExcitatoryNeurons = spn.ExcitatoryNeurons
        let numPNGs, pngAnchorMap, pngDetailMap = CreatePNGAnchorMap spn numExcitatoryNeurons algorithmVersion true

        let maxNeuron, maxCount =
            pngAnchorMap
            |> Map.toSeq
            |> Seq.maxBy (fun (neuron, count) -> count)

        printfn "Total PNGs found: %d" numPNGs
        printfn "Maximum at neuron: %d\tCount: %d" maxNeuron maxCount

        if pngDetailMap.IsSome then
            // merge maps and save to file
            pngDetailMap.Value
            |> Seq.map (fun kvp ->
                    let anchorNeuron = kvp.Key
                    let numPNGsForAnchor = pngAnchorMap.[anchorNeuron]
                    kvp.Value
                    |> Seq.map (fun (numFired, maxLinks, maxTime) ->
                            sprintf "%d %d %d %d %d" anchorNeuron numPNGsForAnchor numFired maxLinks maxTime
                        )
                )
            |> Seq.concat
            |> WriteLinesToFile outputFolder outputFile

        numPNGs

    // Count the number of PNGs found in each state file in this folder
    static let CountPNGsInFolder stateFolder (networkSpecifier:IzhikevichNetworkSpecifier) outputFolder outputFileName baseName algorithmVersion =

        let ScanFiles stateFolder filesMap outputFile =
            let outputPath = Path.Combine(stateFolder, outputFile)
            use sw = File.CreateText(outputPath)
            let resultsMap =
                filesMap
                |> Map.map (fun fileIndex filePath ->
                        let _spn = new IzhikevichNetwork(networkSpecifier.NumExcitatoryNeurons,
                                    networkSpecifier.NumInhibitoryNeurons,
                                    networkSpecifier.NumSynapsesPerNeuron, networkSpecifier.MaxDelay)
                        _spn.LoadNetworkState(filePath)
                        let numPNGs, _, _ = CreatePNGAnchorMap _spn networkSpecifier.NumExcitatoryNeurons algorithmVersion false

                        printfn "Count for file: %d was %d" fileIndex numPNGs
                        sw.WriteLine(sprintf "%d\t%d" fileIndex numPNGs)
                        sw.Flush()
                        numPNGs
                    )

            resultsMap

        let filesMap = GetStateFilesMap stateFolder baseName

        printfn "Starting scanning state files..."
        let resultsMap = ScanFiles stateFolder filesMap (sprintf "%s_Running.txt" baseName)
        printfn "Completed scanning state files"

        // write results to file
        resultsMap
        |> Map.toSeq
        |> Seq.map (fun (fileIndex, numPNGs) -> sprintf "%d\t%d" fileIndex numPNGs)
        |> WriteLinesToFile outputFolder outputFileName

        printfn "Results summary (PNG counts for each file):"
        resultsMap
        |> Map.iter (fun fileIndex numPNGs -> printfn "File %d: %d" fileIndex numPNGs)

        resultsMap

    /// Create a new network with the event bindings needed for saving maturation files
    /// Save network state at the specified save interval (in seconds)
    static member CreateEventBoundNetwork(networkSpecifier, outputFolder, baseName, saveState, saveInterval) =
        CreateEventBoundNetworkImpl networkSpecifier outputFolder baseName saveState saveInterval

    /// Create a new network with the event bindings needed for saving maturation files and collecting PNGs
    static member CreatePNGCollectorNetwork(networkSpecifier:IzhikevichNetworkSpecifier, outputFolder, baseName, tripletCombinations, fBase:StreamWriter, fGroups:StreamWriter, saveState) =
        CreatePNGCollectorNetworkImpl networkSpecifier outputFolder baseName tripletCombinations fBase fGroups saveState

    /// Create a new network with the event bindings needed for collecting the initial triplets of all PNGs in the network
    static member CreatePNGTripletsCollectorNetwork(networkSpecifier:IzhikevichNetworkSpecifier, fTriplets:StreamWriter) =
        CreatePNGTripletsCollectorNetworkImpl networkSpecifier fTriplets

    /// Mature the specified network, saving state files every minute
    static member GenerateMaturationStateFiles(network, inputPattern, runSeconds, backgroundFiringRate, outputFolder, baseName) =
        CreateMatureIzhikevichNetwork network inputPattern runSeconds backgroundFiringRate outputFolder baseName

    /// Mature the network in the specified state file
    /// Save network state at the specified save interval (in seconds)
    static member GenerateMaturationStateFiles(networkSpecifier, inputPattern, inputFolder, stateFile, runSeconds, backgroundFiringRate, outputFolder, baseName, saveState, saveInterval) =
        let network = CreateEventBoundNetworkImpl networkSpecifier outputFolder baseName saveState saveInterval
        network.LoadNetworkState(Path.Combine(inputFolder, stateFile))
        CreateMatureIzhikevichNetwork network inputPattern runSeconds backgroundFiringRate outputFolder baseName

    static member ScanNetworkForActivatedGroups(sampleTime, stateFilePath, outputFilePath, templateList:TemplateList, thresholdPercent, minMatchCount, jitter, networkSpecifier:IzhikevichNetworkSpecifier, inputPattern, runSeconds, backgroundFrequency) =
        let scanner = MatchScanner(networkSpecifier.TotalNeurons, jitter)
        let patternGenerator = new PoissonProcessPatternGenerator(networkSpecifier.TotalNeurons, backgroundFrequency)
        use matchesWriter = File.CreateText(outputFilePath)

        // create a network factory method
        let networkFactory = MatchingNetworkFactory networkSpecifier scanner matchesWriter

        // create a network execution method
        let networkExecutor = MatchingNetworkExecutorSinglePattern inputPattern runSeconds patternGenerator

        // create a network scanner
        let networkScanner =
            NetworkActivationScanner networkFactory networkExecutor scanner templateList thresholdPercent minMatchCount
            //NetworkActivationScanner networkFactory scanner templateList thresholdPercent minMatchCount runSeconds patternGenerator inputPattern sampleTime stateFilePath

        networkScanner sampleTime stateFilePath

    static member ScanNetworkForActivatedGroupsMultiPattern(sampleTime, stateFilePath, outputFilePath, templateList:TemplateList, thresholdPercent, minMatchCount, jitter, networkSpecifier:IzhikevichNetworkSpecifier, stimuli:Stimulus list, secsPerPattern, runSeconds, backgroundFrequency) =
        let scanner = MatchScanner(networkSpecifier.TotalNeurons, jitter)
        let patternGenerator = new PoissonProcessPatternGenerator(networkSpecifier.TotalNeurons, backgroundFrequency)
        use matchesWriter = File.CreateText(outputFilePath)

        // create a network factory method
        let networkFactory = MatchingNetworkFactory networkSpecifier scanner matchesWriter

        // create a network execution method
        let networkExecutor = MatchingNetworkExecutorMultiPattern stimuli runSeconds secsPerPattern patternGenerator

        // create a network scanner
        let networkScanner =
            NetworkActivationScanner networkFactory networkExecutor scanner templateList thresholdPercent minMatchCount

        networkScanner sampleTime stateFilePath

    static member ScanNetworkStateFolderForActivatedGroups(inputFolder, baseName, outputFilePath, templateList:TemplateList, thresholdPercent, minMatchCount, jitter, networkSpecifier:IzhikevichNetworkSpecifier, inputPattern, runSeconds, backgroundFrequency) =
        let scanner = MatchScanner(networkSpecifier.TotalNeurons, jitter)
        let patternGenerator = new PoissonProcessPatternGenerator(networkSpecifier.TotalNeurons, backgroundFrequency)
        use matchesWriter = File.CreateText(outputFilePath)

        // create a network factory method
        let networkFactory = MatchingNetworkFactory networkSpecifier scanner matchesWriter

        // create a network execution method
        let networkExecutor = MatchingNetworkExecutorSinglePattern inputPattern runSeconds patternGenerator

        // create a network scanner
        let networkScanner =
            NetworkActivationScanner networkFactory networkExecutor scanner templateList thresholdPercent minMatchCount

        ScanForActivatedGroupsImpl networkScanner inputFolder baseName

    /// Find all the PNGs in the specified network using the specified algorithm
    /// Return the number of PNGs found and save the PNG details to the specified output file
    static member FindAllPNGsInState(network, outputFolder, outputFileName, algorithmVersion) =
        FindAllPNGs network outputFolder outputFileName algorithmVersion

    /// Find all the PNGs in the specified state file using the specified algorithm
    /// Return the number of PNGs found and save the PNG details to the specified output file
    static member FindAllPNGsInStateFile(inputFolder, stateFile, networkSpecifier:IzhikevichNetworkSpecifier, outputFileName, algorithmVersion) =
        let network = new IzhikevichNetwork(networkSpecifier.NumExcitatoryNeurons,
                        networkSpecifier.NumInhibitoryNeurons,
                        networkSpecifier.NumSynapsesPerNeuron, networkSpecifier.MaxDelay)
        network.LoadNetworkState(Path.Combine(inputFolder, stateFile))
        FindAllPNGs network inputFolder outputFileName algorithmVersion

    /// Find all the PNGs in the current output folder using the specified algorithm
    static member CountPNGsInStateFiles(networkSpecifier, outputFolder, outputFileName, baseName, algorithmVersion) =
        CountPNGsInFolder outputFolder networkSpecifier outputFolder outputFileName baseName algorithmVersion

    /// Scramble the network in the specified state file and return the network
    static member ScrambleNetworkInStateFile(networkSpecifier:IzhikevichNetworkSpecifier, inputFolder, stateFile) =
        let network = new IzhikevichNetwork(networkSpecifier.NumExcitatoryNeurons,
                        networkSpecifier.NumInhibitoryNeurons,
                        networkSpecifier.NumSynapsesPerNeuron, networkSpecifier.MaxDelay)
        network.LoadNetworkState(Path.Combine(inputFolder, stateFile))
        network.Shuffle()

        network

    /// Scramble the network in the specified state file and return an event-bound scrambled network ready for maturation
    /// The event-bound network will save network state at the specified save interval (in seconds)
    static member ScrambleNetworkInStateFile(networkSpecifier, inputFolder, stateFile, outputFolder, baseName, saveState, saveInterval) =
        let network = CreateEventBoundNetworkImpl networkSpecifier outputFolder baseName saveState saveInterval
        network.LoadNetworkState(Path.Combine(inputFolder, stateFile))
        network.Shuffle()

        network

//===========================================================================================================
